package top.rish.rib.base.entry;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.mysql.cj.jdbc.exceptions.MySQLQueryInterruptedException;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.AppContext;
import org.noear.solon.core.BeanWrap;
import org.noear.solon.core.handle.*;
import org.noear.solon.core.route.RouterInterceptor;
import org.noear.solon.core.route.RouterInterceptorChain;
import top.rish.rib.base.ware.BaseService;

import java.lang.reflect.Method;
import java.sql.SQLDataException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLTransientException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component()
public class EntryRouterInterceptor implements RouterInterceptor {


    private final static Map<String, String> MAPPING_IMPL = new ConcurrentHashMap<>();

    static {
        MAPPING_IMPL.put("admin/", "top.rish.rib.ware.%s.service.%sServiceImpl");
    }




    @Override
    public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable {
        long start = System.currentTimeMillis();
        String pathNew = ctx.pathNew()
                .replaceAll("^/", "");
        try {
            List<String> adminBaseMethod = Arrays.stream(pathNew
                    .replaceAll("/+$", "")
                    .split("/")).toList();
            Optional<String> pp = MAPPING_IMPL.keySet().stream().filter(m -> pathNew.startsWith(m) && !pathNew.equals(m)).findFirst();
            if (adminBaseMethod.size() == 4 && ObjectUtil.isAllNotEmpty(adminBaseMethod.get(1), adminBaseMethod.get(2), adminBaseMethod.get(3))
                    && pp.isPresent()) {
                String ppKey = pp.get();
                String module = adminBaseMethod.get(1);
                String interfaces = adminBaseMethod.get(2);
                String method = adminBaseMethod.get(3);
                String baseServiceClass = String.format(MAPPING_IMPL.get(ppKey), module, StrUtil.upperFirst(interfaces));
                AppContext context = Solon.context();
                Map<Class<?>, BeanWrap> map = (Map<Class<?>, BeanWrap>) ReflectUtil.getFieldValue(context, "beanWrapsOfType");
                Class<?> clazz = map.keySet().stream().filter(it -> it.getName().equals(baseServiceClass))
                        .findFirst().orElse(null);
                if (clazz != null && BaseService.class.isAssignableFrom(clazz)) {
                    long mc = Arrays.stream(ReflectUtil.getMethods(clazz)).filter(m ->
                            m.getName().equals(method)
                                    && m.getParameterTypes().length == 1
                                    && m.getParameterTypes()[0] == Context.class
                    ).count();
                    if (mc == 1) {
                        Method m = ReflectUtil.getMethod(clazz, method, Context.class);
                        BeanWrap s = map.get(clazz);
                        try {
                            Object obj = ReflectUtil.invoke(s.raw(), m, ctx);
                            if (obj instanceof Result) {
                                ctx.render(obj);
                            } else {
                                ctx.render(Result.succeed(obj));
                            }
                        } catch (Throwable e) {
                            Throwable cause = e;
                            while (true) {
                                if (cause != null && cause.getCause() != null) {
                                    cause = cause.getCause();
                                } else {
                                    break;
                                }
                            }
                            throw cause;
                        }
                    }
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
            //4.异常捕捉与控制（并标为500错误）
            log.error("{}", e.getMessage());
            ctx.status(500);
            if (e instanceof SQLIntegrityConstraintViolationException) {
                ctx.render(Result.failure(500, "数据不符合约束"));
            } else if (e instanceof SQLSyntaxErrorException) {
                ctx.render(Result.failure(500, "数据操作失败"));
            } else if (e instanceof SQLTransientException) {
                ctx.render(Result.failure(500, "数据事务操作失败"));
            } else if (e instanceof MySQLQueryInterruptedException) {
                ctx.render(Result.failure(500, "数据查询中断"));
            } else if (e instanceof SQLDataException) {
                ctx.render(Result.failure(500, "数据或模型错误"));
            } else if (e instanceof NotLoginException) {
                ctx.status(401);
                ctx.render(Result.failure(401, "请登录"));
            } else if (e instanceof SaTokenException) {
                ctx.render(Result.failure(999_999, e.getMessage()));
            } else {
                ctx.render(Result.failure(500, e.getMessage()));
            }
            return;
        }
        chain.doIntercept(ctx, mainHandler);
        //5.获得接口响应时长
        long times = System.currentTimeMillis() - start;
        log.info("用时：{}ms", times);

    }
}